<?php
/**
 * Func-generator v0.1
 *
 * func-generator 是一款针对 《光宇华夏》数据中心统计后台功能自动生成的程序，用于简化新功能的
 * 的添加过程，减少出错率，提高程序员的工作效率。
 * 工具的使用说明请参考 /docs/README.txt
 * 批处理任务文件规范请参考 /docs/jobs-spec-v0.1.txt
 *
 *
 * @auth: dear@xiaee.com
 * @date: 2011/03/25
 *
 */

require_once('module-utils.php');
require_once('jobs-utils.php');
require_once('simple-logger.php');

/**
 * 控制当前动作是否仅仅是一次尝试性测试
 *
 * 注意：该值受当前正在执行的批处理任务影响，可能被批处理任务动态改变
 *
 */
define('IS_TEST', FALSE);

$logger = new SimpleLogger(dirname(__FILE__).'/../log', 'func-generator');

/**
 * load core config data
 * 加载核心配置文件
 *
 * @param  $cfg_data
 * @return int 出错时返回-1；成功时返回0
 */
function cfg_load_core(&$cfg_data) {
    global $logger;

    $cfg_file_name = '/../config/func-generator.cfg';
    if (!test_file_readable($cfg_file_name)) {
        $logger->error("No core config file '%s' specified!\n", $cfg_file_name);

        return -1;
    }

    $cfg_data = include(dirname(__FILE__).$cfg_file_name);

    return 0;
}


/**
 * load dist config data
 * 加载产品分区配置文件
 *
 * @param  $cfg_data
 * @return int 出错时返回-1；成功时返回0
 */
function cfg_load_dist(&$cfg_data) {
    global $logger;

    $cfg_file_name = '/../config/dist-config.cfg';
    if (!test_file_readable($cfg_file_name)) {
        $logger->error("No dist config file '%s' specified!\n", $cfg_file_name);

        return -1;
    }

    $cfg_data = include(dirname(__FILE__).$cfg_file_name);

    return 0;
}


/**
 * test for database connection
 * 测试数据库连通性
 *
 * @param  $cfg_data
 * @return int 出错时返回-1；成功时返回0
 */
function test_db_connection($cfg_data, &$link) {
    global $logger;

    $db_cfg = $cfg_data['core']['db'];
    $db_schema = strtolower($cfg_data['core']['db']['schema']);
    switch ($db_schema) {
        case 'mysql':
            $link = @mysql_connect($db_cfg['host'].':'.$db_cfg['port'], $db_cfg['user'], $db_cfg['pwd']);
            if (@mysql_select_db($db_cfg['database'], $link) === false) {
                $logger->error("Can't connect to database server or database doesn't exists!\n");

                return -1;
            }

            break;

        default:
            $logger->error("Database schema '%s' is not supported!\n", $db_schema);

            return -1;
    }

    return 0;
}


/**
 * test a file is readable
 * 测试文件是否可读，如果指定文件不存在也会返回false
 * 可选的第二个参数可以指定查找的文件夹路径，默认从当前文件夹下查找
 *
 * @param  $file_name
 * @param null $file_path
 * @return bool
 */
function test_file_readable($file_name, $file_path = null) {
    if (is_null($file_path)) {
        $file_path = dirname(__FILE__);
    }

    $file_path .= $file_name;

    return is_file($file_path) && is_readable($file_path);
}


/**
 * test a file is writable
 * 测试指定文件是否可写，如果指定文件不存在，则测试该文件所属文件夹的可写属性
 * 可选的第二个参数可以指定查找的文件夹路径，默认从当前文件夹下查找
 *
 * @param  $file_name
 * @param null $file_path
 * @return boolean
 */
function test_file_writable($file_name, $file_path = null) {
    if (is_null($file_path)) {
        $file_path = dirname(__FILE__);
    }

    $file_path .= $file_name;
    if (!is_file($file_path)) {
        $file_path = dirname($file_path);
    }

    return is_writable($file_path);
}


/**
 * load batch jobs data
 * 家在批处理任务数据文件
 *
 * @param  $jobs_data
 * @return int 出错时返回-1；成功时返回0
 */
function jobs_load_data($jobs_file_path, &$jobs_data) {
    global $logger;

    if (!is_file($jobs_file_path) || !is_readable($jobs_file_path)) {
        $logger->error("No batch jobs data file '%s' specified!\n", $jobs_file_path);

        return -1;
    }

    $jobs_file = file_get_contents($jobs_file_path);
    if (jobs_parse($jobs_file, $jobs_data) === false) {
        $logger->error("Error occurred during parse jobs file '%s'\n", $jobs_file_path);

        return -1;
    }

    return 0;
}


/**
 * 提交并处理批处理任务
 *
 * @param  $cfg_data 配置数据
 * @param  $jobs_data 任务队列数据
 * @param null $env 环境变量
 * @return int 出错时返回-1；成功时返回0
 */
function jobs_process($cfg_data, $jobs_data, $env = null) {
    global $logger;

    if (is_null($env)) {
        $env = array();
    }

    foreach ($jobs_data as $job) {
        switch ($job['type']) {
            case JOBS_INSTRUCTION_SET_DEFINITION:
                $k = array_rand($job['data']);
                $v = $job['data'][$k];

                // print a message
                $env[$k] = $v;

                break;

            case JOBS_INSTRUCTION_MODULE_ENTRY:
                $rv = jobs_process_module($cfg_data, $job['data'], $env);
                if ($rv == -1) {
                    return -1;
                }

                break;

            default:
                $logger->error("Unsupported batch jobs instruction.\n");

                return -1;
        }
    }

    return 0;
}


/**
 * Check the integrity of the module
 * 检查模块(批处理任务)必要数据的完整性
 *
 * @param  $module_data
 * @return int 出错时返回-1；成功时返回0
 */
function jobs_module_check($module_data) {
    global $logger;

    if (empty($module_data['name'])) {
        $logger->error("Module name is couldn't be empty!\n");

        return -1;
    }

    if (!array_key_exists('dist-code', $module_data)) {
        $logger->error("Module dist is not exists!\n");

        return -1;
    }

    if (!array_key_exists('data-caption', $module_data)) {
        $logger->error("Module data table caption is not exists!\n");

        return -1;
    }

    if (!array_key_exists('process-sql', $module_data)) {
        $logger->error("Module process-sql couldn't be empty!\n");

        return -1;
    }

    if (!array_key_exists('description', $module_data)) {
        $logger->error("Module description is not exists!\n");

        return -1;
    }

    return 0;
}


/**
 * 处理任务的核心函数
 *
 * @param  $cfg_data 配置数据
 * @param  $module_data 模块数据
 * @param null $env 环境变量
 * @return int 出错时返回-1；成功时返回0
 */
function jobs_process_module($cfg_data, $module_data, $env = null) {
    global $logger;

    if (is_null($env)) {
        $env = array();
    }

    if (jobs_module_check($module_data) == -1) {
        return -1;
    }

    /**
     * 准备必要的上下文数据和环境变量
     *
     */
    $env['IS_TEST'] = IS_TEST;
    $env['CURRENT_DATE'] = date('Y-m-d', time());
    $env['CURRENT_TIME'] = date('H:i:s', time());
    $env['PROJECT_PATH'] = realpath($cfg_data['core']['project_path']);

    $context = array(
        'ENV_DATE' => $env['CURRENT_DATE'],
        'ENV_TIME' => $env['CURRENT_TIME']
    );

    do {
        $module_data['uid'] = module_random_name(32);
        $is_module_unique = !is_file($env['PROJECT_PATH'].'/project/'.$module_data['uid'].'.php')
            && !is_file($env['PROJECT_PATH'].'/templates/default/project/'.$module_data['uid'].'.tpl.php');
    } while (!$is_module_unique);

    /**
     *
     * 由于单个功能模块的逻辑都比较小，所以我们没有必要把每个功能块都放在一个单独的逻辑处理文件
     * 里(/project/include/下的类)，所以我们需要一个默认的类文件，以供每次添加逻辑代码时动态
     * 追加新功能逻辑代码。
     *
     */
    $module_data['process-class'] = 'DCRpxxLwZLkEtffP';
    $module_data['dist-id'] = -1;
    foreach ($cfg_data['dist'] as $dist) {
        if ($dist['dist_code'] == $module_data['dist-code']) {
            $module_data['dist-id'] = $dist['dist_id'];

            break;
        }
    }

    // 测试数据库连通性
    if (test_db_connection($cfg_data, $link) == -1) {
        return -1;
    }


    /**
     *
     * 代码生成时需要使用的动态上下文数据
     *
     */
    $context['MODULE_NAME'] = $module_data['name'];
    $context['MODULE_DESCRIPTION'] = $module_data['description'];
    $context['MODULE_DIST_CODE'] = $module_data['dist-code'];
    $context['MODULE_PROCESS_SQL'] = str_replace("\n", "\n        ", mysql_real_escape_string($module_data['process-sql'], $link));
    $context['PARAMS_BEGIN_DATE'] = '\\\'\'.$date1.\'\\\'';
    $context['PARAMS_END_DATE'] = '\\\'\'.$date2.\'\\\'';
    $context['PARAMS_DIST'] = '\\\'\'.$dist.\'\\\'';
    $context['MODULE_DATA_CAPTION'] = ','.$module_data['data-caption'];
    $context['MODULE_PROCESS_CLASS'] = $module_data['process-class'];
    $context['MODULE_PROCESS_METHOD'] = substr($module_data['uid'], 0, 16);
    $context['MODULE_VIEW_TEMPLATE'] = $module_data['uid'];


    $REPLACEMENT_PATTERN = '/(?<!\\\\)\\${(\\w+)}/';
    $template_data = array();

    /**
     *
     * 开始合成"MVC"结构中的 "module" 层代码。
     *
     */
    $template_data['module'] = file_get_contents(dirname(__FILE__).'/../template/module.template');
    while (preg_match($REPLACEMENT_PATTERN, $template_data['module'], $m)) {
        if (array_key_exists($m[1], $context)) {
            $template_data['module'] = preg_replace('/(?<!\\\\)\\${'.preg_quote($m[1]).'}/', $context[$m[1]], $template_data['module']);
        }
        else {
            $logger->error("Environment variable '%s' not found in module template file!\n", $m[1]);

            return -1;
        }
    }

    /**
     *
     * 测试模型代码文件的可写权限。
     *
     */
    if (!test_file_writable('/project/'.$module_data['uid'].'.php', $env['PROJECT_PATH'])) {
        $logger->error("Failed to write module file: Permission denied!\n");

        return -1;
    }


    /**
     *
     * 开始合成"MVC"结构中的 "view" 层代码。
     *
     */
    $template_data['view'] = file_get_contents(dirname(__FILE__).'/../template/view.template');
    while (preg_match($REPLACEMENT_PATTERN, $template_data['view'], $m)) {
        if (array_key_exists($m[1], $context)) {
            $template_data['view'] = preg_replace('/(?<!\\\\)\\${'.preg_quote($m[1]).'}/', $context[$m[1]], $template_data['view']);
        }
        else {
            $logger->error("Warning: Environment variable '%s' not found in view template file!\n", $m[1]);

            return -1;
        }
    }

    if (!test_file_writable('/templates/default/project/'.$module_data['uid'].'.tpl.php', $env['PROJECT_PATH'])) {
        $logger->error("Failed to write module file: Permission denied!\n");

        return -1;
    }


    /**
     *
     * 开始合成"MVC"结构中的 "controller" 层代码。
     *
     */
    $template_data['controller'] = file_get_contents(dirname(__FILE__).'/../template/controller.template');
    while (preg_match($REPLACEMENT_PATTERN, $template_data['controller'], $m)) {
        if (array_key_exists($m[1], $context)) {
            $template_data['controller'] = preg_replace('/(?<!\\\\)\\${'.preg_quote($m[1]).'}/', $context[$m[1]], $template_data['controller']);
        }
        else {
            $logger->error("Environment variable '%s' not found in controller template file!\n", $m[1]);

            return -1;
        }
    }

    if (!is_file($env['PROJECT_PATH'].'/project/include/'.$module_data['process-class'].'.class.php')) {
        $class_file = file_get_contents(dirname(__FILE__).'/../template/class.template');
        while (preg_match($REPLACEMENT_PATTERN, $class_file, $m)) {
            if (array_key_exists($m[1], $context)) {
                $class_file = preg_replace('/(?<!\\\\)\\${'.preg_quote($m[1]).'}/', $context[$m[1]], $class_file);
            }
            else {
                $logger->error("Environment variable '%s' not found in class template file!\n", $m[1]);

                return -1;
            }
        }
    }
    else {
        $class_file = file_get_contents($env['PROJECT_PATH'].'/project/include/'.$module_data['process-class'].'.class.php');
        $class_file = iconv('GBK', 'UTF-8', $class_file);
    }

    $class_file = rtrim($class_file);
    if (strrpos($class_file, '?>') !== false) {
        $class_file = rtrim(substr($class_file, 0, strrpos($class_file, '?>')));
    }
    if (strrpos($class_file, '}') !== false) {
        $class_file = rtrim(substr($class_file, 0, strrpos($class_file, '}')));
    }

    $class_file .= "\n\n\n";
    $class_file .= $template_data['controller'];
    $class_file .= "\n}\n";
    $template_data['controller'] = $class_file;

    if (!test_file_writable('/project/include/'.$module_data['process-class'].'.class.php', $env['PROJECT_PATH'])) {
        $logger->error("Failed to write module file: Permission denied!\n");

        return -1;
    }


    /**
     *
     * 选择数据库，准备在数据库注册新功能模块。
     *
     */
    if (@mysql_select_db($cfg_data['core']['db']['database'], $link) === false) {
        $logger->error("Can't connect to database server with error:\n%s\n", mysql_error($link));

        return -1;
    }

    $register_module_sql = 'INSERT INTO project (`title`, `scriptname`, `cat_id`, `create_time`, `list_order`, `target`, `is_show`, `memo`) VALUES (\''.mysql_real_escape_string($module_data['name']).'\', \''.$module_data['uid'].'.php?game='.$module_data['dist-code'].'\', '.$module_data['dist-id'].', \''.date('Y-m-d H:i:s', time()).'\', 0, \'mainFrame\', 1, \''.mysql_real_escape_string($module_data['description']).'\')';
    if ($env['IS_TEST']) {
        $register_module_sql = 'PREPARE stmt FROM \''.mysql_real_escape_string($register_module_sql).'\'';
    }

    /**
     *
     * 需要注意的是：我们目前项目数据库使用的是latin1的字符集，我们需要在提交数据之前做一次转码
     * 把 UTF-8 编码的数据转换成 GBK 编码的数据，UTF-8的字符集范围要大于GBK的字符集范围。
     * 所以我们需要使用"//TRANSLIT"选项将转换失败的字符使用最接近的字符替换，保证数据的基本完
     * 整性。
     *
     */
    if (mysql_query(iconv('UTF-8', 'GBK//TRANSLIT', $register_module_sql), $link) === false) {
        $logger->error("Database error:\n%s\n", mysql_error($link));

        return -1;
    }


    /**
     *
     * 到这里，我们已经基本确定了所有的步骤都进行得十分顺利，可以提交新功能块的添加任务了！
     *
     */
    if (!$env['IS_TEST']) {
        file_put_contents($env['PROJECT_PATH'].'/project/'.$module_data['uid'].'.php', iconv('UTF-8', 'GB2312', $template_data['module']));
        file_put_contents($env['PROJECT_PATH'].'/templates/default/project/'.$module_data['uid'].'.tpl.php', iconv('UTF-8', 'GB2312', $template_data['view']));
        file_put_contents($env['PROJECT_PATH'].'/project/include/'.$module_data['process-class'].'.class.php', iconv('UTF-8', 'GB2312', $template_data['controller']));

        $file_list = scandir($env['PROJECT_PATH'].'/data/cache');
        foreach ($file_list as $cache_file) {
            if (in_array($cache_file, array('.', '..', ''))) {
                continue;
            }

            /**
             *
             * 最后，我们需要把 Ucenter 需要的缓存文件清除掉，以便使我们的修改立即生效！
             *
             */
            @unlink($env['PROJECT_PATH'].'/data/cache/'.$cache_file);
        }
    }

    return 0;
}



